/*******************************************************************************
 * Copyright (c) 2007 University of Oslo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     University of Oslo - initial API and implementation
 *******************************************************************************/

package com.cea.papyrus.diagram.sequence.sedi.edit.handlers;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.Status;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.requests.CreateConnectionRequest;
import org.eclipse.gef.requests.ReconnectRequest;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.uml2.uml.InteractionOperand;

import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.sequence.sedi.edit.SeDiSubpartEditPart;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.AnalyseOwnerCommand;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.AnalyseSeparatorPositionsCommand;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.RequestUtils;
import com.cea.papyrus.diagram.sequence.sedi.figures.AnchoringFigure;
import com.cea.papyrus.diagram.sequence.sedi.figures.SeDiAnchor;
import com.cea.papyrus.diagram.sequence.sedi.model.command.CreateGraphicalElementCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.SplitInteractionOperandCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.UnionInteractionOperandsCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.diagram.SetAnchorHintsCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil;
import com.cea.papyrus.diagraminterchange2.di2.Di2Factory;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphConnector;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * @author Andreas Limyr
 * @author frankd@ifi.uio.no
 */
public class SeparatorHandler implements LinkHandler {

    public final EditPolicy hostPolicy;

    public SeparatorHandler(final EditPolicy hostPolicy) {
        this.hostPolicy = hostPolicy;
    }

    @SuppressWarnings("unchecked")
    public Command[] getCreateCommands(CreateConnectionRequest request) {
        // AnalyseOwnerCommand [SeDi]
        // Find the operand that will be split by the separator
        // AnalyseSeparatorPositionsCommand [SeDi]
        // Find out how the fragments inside this operand will be
        // distributed over the split operands
        // SetAnchorHintsCommand [SeDi]
        // Record hints about the location of the graphical separator
        // SplitInteractionOperandCommand [model]
        // Split the operand in two. This will consume the graphical hints
        // registered with the previous command.

        final SeDiSubpartEditPart cfragmentEP = (SeDiSubpartEditPart) hostPolicy.getHost();
        final GraphNode cfragmentView = (GraphNode) cfragmentEP.getModel();
        final SeDiAnchor startAnchor = (SeDiAnchor) request.getExtendedData().get("SourceAnchor");
        final SeDiAnchor targetAnchor = (SeDiAnchor) cfragmentEP.getTargetConnectionAnchor(request);
        final Point startLocation = startAnchor.getLocation(null);

        final AnalyseOwnerCommand ownerCmd = new AnalyseOwnerCommand();
        ownerCmd.setDiagram(LookForElement.getDiagram(cfragmentView));
        ownerCmd.setLocation(startLocation);
        ownerCmd.setAnyEditPart(cfragmentEP);

        final AnalyseSeparatorPositionsCommand analysePosCmd =
                new AnalyseSeparatorPositionsCommand();
        analysePosCmd.setAnyEditPart(cfragmentEP);
        analysePosCmd.setSeparatorY(startAnchor.getLocation(null).y);

        final SplitInteractionOperandCommand splitCmd = new SplitInteractionOperandCommand();

        final SetAnchorHintsCommand hintCmd = new SetAnchorHintsCommand();
        hintCmd.setTargetHint(targetAnchor.getTag());

        final CreateGraphicalElementCommand createGECmd = new CreateGraphicalElementCommand();
        createGECmd.setAnyEditPart(hostPolicy.getHost());
        createGECmd.setParentGE(cfragmentView);

        final Command glue1 = new Command() {
            public void execute() {
                InteractionOperand splitOperand = (InteractionOperand) ownerCmd.getOwner();
                analysePosCmd.setInteractionOperand(splitOperand);
                splitCmd.setInteractionOperand(splitOperand);
            }

            /**
             * @see org.eclipse.gef.commands.Command#redo()
             */
            @Override
            public void redo() {
                // does nothing.
            }
        };

        final Command glue2 = new Command() {
            public void execute() {
                splitCmd.setPositions(analysePosCmd.getPositions());
            }

            /**
             * @see org.eclipse.gef.commands.Command#redo()
             */
            @Override
            public void redo() {
                // does nothing
            }
        };

        final Command glueGE = new Command() {
            @Override
            public void execute() {
                createGECmd.setElement(splitCmd.getLowerOperand());
            }

            /**
             * @see org.eclipse.gef.commands.Command#redo()
             */
            @Override
            public void redo() {
                // does nothing.
            }

        };

        final Command setPosition = new Command() {
            @Override
            public void execute() {
                GraphEdge edge = ModelUtil.getGraphEdge(splitCmd.getLowerOperand(), cfragmentView);
                edge.getAnchor().get(0).setPosition(
                        new Point(startAnchor.getOffsetX(), startAnchor.getOffsetY()));
                edge.getAnchor().get(1).setPosition(
                        new Point(targetAnchor.getOffsetX(), targetAnchor.getOffsetY()));
            }

            /**
             * @see org.eclipse.gef.commands.Command#undo()
             */
            @Override
            public void undo() {
                super.undo();
            }

            /**
             * @see org.eclipse.gef.commands.Command#redo()
             */
            @Override
            public void redo() {
                super.redo();
            }
        };

        return new Command[] {
                ownerCmd, glue1, analysePosCmd, hintCmd, glue2, splitCmd, glueGE, createGECmd,
                setPosition
        };
    }

    @SuppressWarnings("unchecked")
    public Command[] getDeleteCommands(Request request) {
        final List<Command> cmd = new LinkedList<Command>();

        // The operand to delete
        final GraphEdge view = (GraphEdge) hostPolicy.getHost().getModel();
        final InteractionOperand operand = ModelUtil.<InteractionOperand> getSemantic(view);

        // Some useful references
        final Diagram diagram = LookForElement.getDiagram(view);
        final Map<Object, EditPart> editPartRegistry =
                (Map<Object, EditPart>) hostPolicy.getHost().getViewer().getEditPartRegistry();

        // delete the guard, if any
        if (operand.getGuard() != null) {
            EditPart guardEP =
                    editPartRegistry.get(ModelUtil.getGraphElement(operand.getGuard(), diagram));
            Request delReq = RequestUtils.createDeleteRequest(guardEP);
            cmd.add(guardEP.getCommand(delReq));
        }

//        final DeleteGraphicalLinkCommand delCmd = new DeleteGraphicalLinkCommand();
//        delCmd.setDiagram(diagram);
//        delCmd.setLink(operand);
//        cmd.add(delCmd);

        // move the interaction fragments into the operand above
        final UnionInteractionOperandsCommand unionCmd = new UnionInteractionOperandsCommand();
        unionCmd.setInteractionOperand(operand);
        cmd.add(unionCmd);

        // cmd.add(new DeleteInteractionOperandCommand(view.getTypedElement()));
//        cmd.add(delCmd.getHintCommand());

        return (Command[]) cmd.toArray(new Command[cmd.size()]);
    }

    @SuppressWarnings("unchecked")
    public Command[] getReconnectSourceCommands(ReconnectRequest request) {
        // DeleteGraphicalLinkCommand [SeDi]
        // Records the current state in case of undo
        // UnionInteractionOperandsCommand [SeDi]
        // Join the two operands on each side of the separator
        // AnalyseOwnerCommand [SeDi]
        // Find the operand that will be split by the separator in
        // its new position [by the new separator]
        // AnalyseSeparatorPositionsCommand [SeDi]
        // Find out how the fragments inside this operand will be
        // distributed over the split operands
        // SetAnchorHintsCommand [SeDi]
        // Record hints about the location of the graphical separator
        // SplitInteractionOperandCommand [model]
        // Split the operand in two. This will consume the graphical hints
        // registered with the previous command.
        final ConnectionEditPart separatorEP = (ConnectionEditPart) request.getConnectionEditPart();
        final GraphEdge link = (GraphEdge) separatorEP.getModel();

        final SeDiSubpartEditPart cfragmentEP = (SeDiSubpartEditPart) hostPolicy.getHost();
        // final GraphicalElement newSourceModel = (GraphicalElement)
        // cfragmentEP.getModel();
        final SeDiAnchor newSourceAnchor =
                (SeDiAnchor) cfragmentEP.getSourceConnectionAnchor(request);
        final Point newSourceLocation = newSourceAnchor.getLocation(null);

        // if (!newSourceAnchor.acceptReconnectSource(request)
        // || newSourceAnchor.getTag().equals(link.getSourceRef()))
        // return null;
        // Already tested for acceptance

//        final Command delViewCmd =
//                new DeleteGraphicalLinkCommand(LookForElement.getDiagram(link), ModelUtil
//                        .<NamedElement> getSemantic(link));

        final UnionInteractionOperandsCommand unionCmd = new UnionInteractionOperandsCommand();
        unionCmd.setInteractionOperand(ModelUtil.<InteractionOperand> getSemantic(link));

        final AnalyseOwnerCommand ownerCmd = new AnalyseOwnerCommand();
        ownerCmd.setDiagram(LookForElement.getDiagram(link));
        ownerCmd.setLocation(newSourceLocation);
        ownerCmd.setAnyEditPart(cfragmentEP);

        final AnalyseSeparatorPositionsCommand analysePosCmd =
                new AnalyseSeparatorPositionsCommand();
        analysePosCmd.setAnyEditPart(cfragmentEP);
        analysePosCmd.setSeparatorY(newSourceAnchor.getLocation(null).y);

        final SplitInteractionOperandCommand splitCmd = new SplitInteractionOperandCommand();
        splitCmd.setConstraint(ModelUtil.<InteractionOperand> getSemantic(link).getGuard());
        final GraphNode contraintNode =
                ModelUtil.getGraphNode(ModelUtil.<InteractionOperand> getSemantic(link).getGuard(),
                        LookForElement.getDiagram(link));

        final Command glue1 = new Command() {
            public void execute() {
                InteractionOperand splitOperand = (InteractionOperand) ownerCmd.getOwner();
                analysePosCmd.setInteractionOperand(splitOperand);
                splitCmd.setInteractionOperand(splitOperand);
            }
        };

        final Command glue2 = new Command() {
            public void execute() {
                splitCmd.setPositions(analysePosCmd.getPositions());
            }
        };

        // Handle movement of target end

        // Calculate the horizontal difference for the source anchor
        // Then look for an anchor the same distance from the original target
        // TODO Refactor: this code can be shared with MessageHandler
        final String oldSourceRef = ModelUtil.getSourceRef(link);
        // final SeDiSubpartEditPart oldSourceEP = (SeDiSubpartEditPart)
        // separatorEP.getSource();
        final ConnectionAnchor oldSourceAnchor = cfragmentEP.getConnectionAnchor(oldSourceRef);

        final Point oldSourceLocation = oldSourceAnchor.getLocation(null);
        SeDiAnchor oldSourceSeDiAnchor = (SeDiAnchor) oldSourceAnchor;
        final Point oldSourcePosition =
                new Point(oldSourceSeDiAnchor.getOffsetX(), oldSourceSeDiAnchor.getOffsetY());
        final int distance = newSourceLocation.y - oldSourceLocation.y;
        final SeDiAnchor oldTargetSeDiAnchor =
                (SeDiAnchor) cfragmentEP.getConnectionAnchor(ModelUtil.getTargetRef(link));
        final Point oldTargetPosition =
                new Point(oldTargetSeDiAnchor.getOffsetX(), oldTargetSeDiAnchor.getOffsetY());

        // final SeDiSubpartEditPart targetEP = (SeDiSubpartEditPart)
        // separatorEP.getTarget();
        final AnchoringFigure cfFigure = (AnchoringFigure) cfragmentEP.getFigure();
        final String newTargetRef =
                cfFigure.getBestTargetAnchor(ModelUtil.getTargetRef(link), distance,
                        newSourceLocation.x);
        final SeDiAnchor newTargetAnchor =
                (SeDiAnchor) ((AnchoringFigure) cfragmentEP.getFigure()).getAnchor(newTargetRef);

        final SetAnchorHintsCommand hintCmd = new SetAnchorHintsCommand();
        hintCmd.setTargetHint(newTargetRef);

        Command setPosition = new Command() {
            /**
             * @see org.eclipse.gef.commands.Command#execute()
             */
            @Override
            public void execute() {
                link.getAnchor().get(0).setPosition(
                        new Point(newSourceAnchor.getOffsetX(), newSourceAnchor.getOffsetY()));
                link.getAnchor().get(1).setPosition(
                        new Point(newTargetAnchor.getOffsetX(), newTargetAnchor.getOffsetY()));
            }

            /**
             * @see org.eclipse.gef.commands.Command#undo()
             */
            @Override
            public void undo() {
                link.getAnchor().get(0).setPosition(oldSourcePosition);
                link.getAnchor().get(1).setPosition(oldTargetPosition);
            }

            /**
             * @see org.eclipse.gef.commands.Command#redo()
             */
            @Override
            public void redo() {
                link.getAnchor().get(0).setPosition(
                        new Point(newSourceAnchor.getOffsetX(), newSourceAnchor.getOffsetY()));
                link.getAnchor().get(1).setPosition(
                        new Point(newTargetAnchor.getOffsetX(), newTargetAnchor.getOffsetY()));
            }
        };

        Command restoreEdge = new Command() {
            /**
             * @see org.eclipse.gef.commands.Command#execute()
             */
            @Override
            public void execute() {
                new UIJob("restore edge") {
                    public org.eclipse.core.runtime.IStatus runInUIThread(
                            org.eclipse.core.runtime.IProgressMonitor monitor) {
                        GraphEdge graphEdge = Di2Factory.eINSTANCE.createGraphEdge();
                        Uml1SemanticModelBridge uml1SemanticModelBridge =
                                Di2Factory.eINSTANCE.createUml1SemanticModelBridge();
                        uml1SemanticModelBridge.setElement(splitCmd.getLowerOperand());
                        graphEdge.setSemanticModel(uml1SemanticModelBridge);

                        GraphConnector targetConnector =
                                Di2Factory.eINSTANCE.createGraphConnector();
                        GraphConnector sourceConnector =
                                Di2Factory.eINSTANCE.createGraphConnector();

                        sourceConnector
                                .setPosition(link.getAnchor().get(0).getPosition().getCopy());
                        targetConnector
                                .setPosition(link.getAnchor().get(1).getPosition().getCopy());
                        graphEdge.getAnchor().add(sourceConnector);
                        graphEdge.getAnchor().add(targetConnector);

                        if (splitCmd.getLowerOperand().getGuard() != null) {
                            Point position = contraintNode.getPosition().getCopy();
                            position.y = contraintNode.getPosition().y + distance;
                            contraintNode.setPosition(position);
                        }

                        ((GraphElement) cfragmentEP.getModel()).getContained().add(graphEdge);
                        ((GraphElement) cfragmentEP.getModel()).getAnchorage().add(
                                graphEdge.getAnchor().get(0));
                        ((GraphElement) cfragmentEP.getModel()).getAnchorage().add(
                                graphEdge.getAnchor().get(1));
                        return Status.OK_STATUS;
                    }
                }.schedule();
            }

            /**
             * @see org.eclipse.gef.commands.Command#undo()
             */
            @Override
            public void undo() {
                execute();
            }

            /**
             * @see org.eclipse.gef.commands.Command#redo()
             */
            @Override
            public void redo() {
                execute();
            }
        };

        return new Command[] {
//                delViewCmd, unionCmd, ownerCmd, glue1, analysePosCmd, hintCmd, glue2, splitCmd,
//                setPosition, restoreEdge
                unionCmd, ownerCmd, glue1, analysePosCmd, hintCmd, glue2, splitCmd,
                setPosition, restoreEdge
        };
    }

    public Command[] getReconnectTargetCommands(ReconnectRequest request) {
        return null; // not allowed
    }
}
